EDA#
Librerias#
import pandas as pd
import os
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import missingno as msno
import matplotlib.pyplot as plt
import seaborn as sns
import sweetviz as sv
Cargar datos y Limpieza de dataset#
Cargar datos#
Ruta de la carpeta que contiene los archivos CSV
carpeta = ‘C:/UNINORTE/VC/Proyecto2/dataset_ventas’
Obtener la lista de archivos CSV en la carpeta
archivos_csv = [archivo for archivo in os.listdir(carpeta) if archivo.endswith(‘.csv’)]
Crear un DataFrame vacío para almacenar los datos combinados
ventas = pd.DataFrame()
Leer cada archivo CSV y combinarlo en el DataFrame datos_combinados
for archivo in archivos_csv: ruta_archivo = os.path.join(carpeta, archivo) datos_archivo = pd.read_csv(ruta_archivo, index_col=0) ventas = pd.concat([ventas, datos_archivo], ignore_index=True)
Descargar los datos en un excel
ventas.to_excel(‘datos.xlsx’, index=False)
Despues de hacer merch entre todos los documentos csv, se hace una descarga del archivo final y de aquí en adelante usaremos este archivo.
ventas = pd.read_excel('C:/UNINORTE/VC/Proyecto2/datos.xlsx')
ventas.head(10)
| lat | long | id | date | category | location | mode | price | details | description | surface | rooms | baths | park | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 1 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 2 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 3 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 4 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
| 5 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
| 6 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
| 7 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
| 8 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
| 9 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
Limpiza de datos#
# Reemplazar "Aconsultar" por NaN en la columna price
ventas['price'] = ventas['price'].replace('Aconsultar', np.nan)
# Eliminar el signo $, los puntos y los espacios en blanco de la columna price
ventas['price'] = ventas['price'].str.replace('[\$,\. ]', '', regex=True)
# Convertir la columna price a tipo numérico
ventas['price'] = pd.to_numeric(ventas['price'])
# Convertir la columna date a formato de fecha
ventas['date'] = pd.to_datetime(ventas['date'])
# Establecer valores fuera del rango válido como NaN en la columna 'lat', es decir, todos lo valores que esten fuera de las latitudes mínima y máxima de Colombia
ventas.loc[(ventas['lat'] < -4.227) | (ventas['lat'] > 12.450), 'lat'] = np.nan
# Establecer valores fuera del rango válido como NaN en la columna 'long', es decir, todos lo valores que esten fuera de las longitudes mínima y máxima de Colombia
ventas.loc[(ventas['long'] < -79.000) | (ventas['long'] > -67.000), 'long'] = np.nan
# Dividir la columna 'location' en dos nuevas columnas: 'ciudad' y 'barrio'
ventas[['ciudad', 'barrio']] = ventas['location'].str.split(' ', n=1, expand=True)
# Eliminar la columna 'location'
ventas.drop(columns=['location'], inplace=True)
# Eliminar "_x000D_" de las columnas especificadas
cols_to_clean = ['surface', 'rooms', 'baths', 'park']
for col in cols_to_clean:
ventas[col] = ventas[col].str.replace('_x000D_', '', regex=False)
# Eliminar "m²", puntos y comas de la columna 'surface', convertir a formato numérico y dividir por 100 para que se tenga en cuenta los decimales
ventas['surface'] = ventas['surface'].str.replace('m²', '', regex=False).str.replace('.', '', regex=False).str.replace(',', '', regex=False).astype(float) / 100
# Reemplazar "Sinespecificar" por NaN y eliminar "Habitaciones:" de la columna 'rooms'
ventas['rooms'] = ventas['rooms'].replace('Sinespecificar', np.nan).str.replace('Habitaciones:', '', regex=False).astype(float)
# Reemplazar "Sinespecificar" por NaN y eliminar "Baños:" de la columna 'baths'
ventas['baths'] = ventas['baths'].replace('Sinespecificar', np.nan).str.replace('Baños:', '', regex=False).astype(float)
# Limpiar y transformar la columna 'park'
ventas['park'] = ventas['park'].replace('Sinespecificar', '0')\
.str.replace('Parqueaderos:', '', regex=False)\
.replace('Másde10', '11')\
.astype(float)
# Eliminar filas donde 'mode' es "Arriendo"
ventas = ventas[ventas['mode'] != 'Arriendo']
# Eliminar la columna 'mode'
ventas.drop(columns=['mode'], inplace=True)
# Palabras a eliminar
words_to_remove = ['VENTA', 'VENDO', 'APTO', 'BARRIO']
# Eliminar palabras específicas y repetición del nombre de la ciudad
ventas['barrio'] = ventas.apply(lambda row: ' '.join(word for word in (row['barrio'] or '').split() if word.upper() not in words_to_remove and word.upper() != row['ciudad'].upper()), axis=1)
A continuación, trataremos por separado la columna details, dado que tiene muchos datos útiles para el análisis, pero están juntos dentro de una misma columna y de forma que no se pueden interpretar correctamente
# Crear un nuevo DataFrame solo con la columna 'details'
nuevo_df = pd.DataFrame(ventas['details'])
# Dividir la columna 'details' en varias columnas separando el texto por '''
nuevo_df = nuevo_df['details'].str.split("'", expand=True)
# Renombrar las columnas del nuevo DataFrame
nuevo_df.columns = [f'detalle_{i}' for i in range(nuevo_df.shape[1])]
# Lista de columnas a eliminar
columnas_a_eliminar = ['detalle_0', 'detalle_2', 'detalle_4', 'detalle_6', 'detalle_8', 'detalle_10', 'detalle_12', 'detalle_14', 'detalle_16', 'detalle_18', 'detalle_20']
# Eliminar las columnas especificadas
nuevo_df.drop(columns=columnas_a_eliminar, inplace=True)
# Nombres de las columnas del nuevo DataFrame
nombres_columnas = ["Área privada", "Área Const.", "Precio m²", "Admón", "Estrato", "Estado", "Antigüedad", "Piso No", "Tipo de Apartamento", "Sector"]
# Crear el nuevo DataFrame con las columnas especificadas
df_final = pd.DataFrame(columns=nombres_columnas)
# Función para extraer el valor asociado a un nombre de columna en una fila del DataFrame original
def extraer_valor(fila, nombre_columna):
for elemento in fila:
if isinstance(elemento, str) and nombre_columna.lower() in elemento.lower():
return elemento.split(':')[1].strip()
return None
# Llenar el nuevo DataFrame buscando los valores correspondientes en cada fila de nuevo_df
for nombre_columna in nombres_columnas:
df_final[nombre_columna] = nuevo_df.apply(lambda fila: extraer_valor(fila, nombre_columna), axis=1)
df_final.head()
| Área privada | Área Const. | Precio m² | Admón | Estrato | Estado | Antigüedad | Piso No | Tipo de Apartamento | Sector | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 1 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 2 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 3 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 4 | None | \r 65,00 m² | \r 3.307.692/m² | None | \r 4\r \r | None | None | None | None | Ver Mapa |
A continuación, concatenar esta nueva información con nuestro dataframe ‘ventas’
# Concatenar los DataFrames a lo largo del eje de las columnas
ventas = pd.concat([ventas, df_final], axis=1)
Eliminaremos las siguientes columnas:
‘details’: La información contenida en esta columna ya está divida en otras columnas (pasos realizados anteriormente).
‘description’: Es un valor único para cada vivienda y es subjetivo.
‘date’: Es la fecha en la que se cargó la información de la vivienda a la pagina, lo cual no influye en el costo de la vivienda.
‘id’: Es el código asignado a la vivienda al momento del registro y es un valor único, no influye en el costo de la vivienda.
‘Precio m²’: Es una columna calculada a partir de la columna ‘price’ y ‘Área const.’.
# Eliminar la columna 'details'
ventas.drop(columns=['details'], inplace=True)
# Eliminar la columna 'description'
ventas.drop(columns=['description'], inplace=True)
# Eliminar la columna 'date'
ventas.drop(columns=['date'], inplace=True)
# Eliminar la columna 'Precio m²'
ventas.drop(columns=['Precio m²'], inplace=True)
# Limpieza y conversión de las columnas "Área privada" y "Área Const." en el DataFrame ventas
for columna in ["Área privada", "Área Const."]:
ventas[columna] = (ventas[columna]
.str.replace('m²', '', regex=False)
.str.replace('.', '', regex=False)
.str.replace(',', '', regex=False)
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.strip()
.astype(float) / 100)
# Limpieza de la columna "Admón"
ventas['Admón'] = (ventas['Admón']
.str.replace('$', '', regex=False)
.str.replace(',', '', regex=False)
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.replace('Incluida', '0', regex=False)
.str.strip()
.astype(float))
# Limpieza de la columna "Estrato"
ventas['Estrato'] = (ventas['Estrato']
.str.strip()
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.replace('Campestre', np.nan)
.replace(' Campestre ', np.nan)
.astype(float))
# Limpieza de la columna "Estado"
ventas['Estado'] = (ventas['Estado']
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.strip())
# Mostrar los valores únicos en la columna "Estado"
valores_unicos_estado = ventas['Estado'].unique()
print(valores_unicos_estado)
[None 'Bueno' 'Excelente' 'Remodelar']
A continuación, cambiaremos los valores de la columna estado para pasarlos a números, teniendo en cuenta que esto es una calificación de la vivienda.
# Cambiar los valores en la columna "Estado"
ventas['Estado'] = ventas['Estado'].replace({'Remodelar': 3, 'Bueno': 4, 'Excelente': 5})
C:\Users\Linda Herrera\AppData\Local\Temp\ipykernel_17900\1796510237.py:2: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
ventas['Estado'] = ventas['Estado'].replace({'Remodelar': 3, 'Bueno': 4, 'Excelente': 5})
# Cambiar el nombre de la columna "Antigüedad" a "Antiguedad"
ventas.rename(columns={'Antigüedad': 'Antiguedad'}, inplace=True)
# Limpieza de la columna "Antigüedad"
ventas['Antiguedad'] = (ventas['Antiguedad']
.str.replace('\\r', '', regex=False)
.str.strip())
# Limpieza de la columna "Piso No"
ventas['Piso No'] = (ventas['Piso No']
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.replace('º', '', regex=False)
.str.replace('ª', '', regex=False)
.str.strip()
.replace('Otros', np.nan)
.astype(float))
# Limpieza de la columna "Tipo de Apartamento"
ventas['Tipo de Apartamento'] = (ventas['Tipo de Apartamento']
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.strip())
# Cambiar valores en 'Tipo de Apartamento' a 'No Aplica' cuando 'category' es 'Casa'
ventas.loc[ventas['category'] == 'Casa', 'Tipo de Apartamento'] = 'No Aplica'
# Limpieza de la columna "Sector"
ventas['Sector'] = (ventas['Sector']
.str.replace('Ver Mapa', '', regex=False)
.replace('', np.nan) # Reemplazar cadenas vacías resultantes con NaN
.str.strip())
# Restablecer el índice del DataFrame
ventas = ventas.reset_index(drop=True)
Eliminar columnas duplicadas y eliminar la columna:
Los duplicados se eliminarán de acuerdo con los id de vivienda duplicados y teniendo en cuenta que las filas que queden sean las más completas.
‘id’: Es el código asignado a la vivienda al momento del registro y es un valor único, no influye en el costo de la vivienda.
# Eliminar filas duplicadas manteniendo la fila con más datos completos - Para aseguridad de que un mismo inmueble no tuviera 2 id diferentes
ventas = (ventas.sort_values(by=ventas.columns.tolist(), na_position='last')
.drop_duplicates(subset=['id'], keep='first')
.drop_duplicates())
# Eliminar la columna 'id'
ventas.drop(columns=['id'], inplace=True)
ventas.head(10)
| lat | long | category | price | surface | rooms | baths | park | ciudad | barrio | Área privada | Área Const. | Admón | Estrato | Estado | Antiguedad | Piso No | Tipo de Apartamento | Sector | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 407717 | -3.556624 | NaN | Apartamento | 197000000.0 | 64.0 | 2.0 | 2.0 | 1.0 | Cali | Ciudad Bochalema | 64.0 | 64.0 | 178000.0 | 4.0 | NaN | 1 a 8 años | NaN | None | Ciudad Bochalema |
| 407715 | -2.889093 | NaN | Casa | 650000000.0 | 192.0 | 5.0 | 3.0 | 0.0 | Bogotá | Antiguo Copihue | 192.0 | 192.0 | NaN | 3.0 | NaN | 1 a 8 años | NaN | No Aplica | Zona Norte |
| 407713 | -2.577413 | -73.038536 | Apartaestudio | 70000000.0 | 30.0 | 1.0 | 1.0 | 0.0 | Bogotá | Las Lomas | 30.0 | 30.0 | 45000.0 | 2.0 | NaN | 16 a 30 años | NaN | None | Las Lomas |
| 407710 | -2.193166 | NaN | Apartamento | 235000000.0 | 56.0 | 3.0 | 2.0 | 1.0 | Medellín | Calasanz Occidente | 56.0 | 56.0 | 192139.0 | 3.0 | NaN | None | 13.0 | None | NaN |
| 407708 | -2.037440 | NaN | Apartaestudio | 139000000.0 | 43.0 | 1.0 | 1.0 | 1.0 | Barranquilla | Ciudad Jardín | 43.0 | 43.0 | 145000.0 | 4.0 | NaN | None | 4.0 | None | NaN |
| 407706 | -1.054628 | -73.300781 | Apartamento | 260000000.0 | 115.0 | 4.0 | 2.0 | 0.0 | Medellín | Centro | NaN | 115.0 | NaN | 4.0 | 4.0 | 9 a 15 años | 3.0 | None | Centro |
| 407703 | -0.341669 | -78.530228 | Apartamento | 175000000.0 | 63.0 | 3.0 | 2.0 | 1.0 | Cartagena | Ciudad Jardin | 63.0 | 63.0 | 115000.0 | 3.0 | NaN | None | 7.0 | None | NaN |
| 407700 | -0.181263 | NaN | Apartamento | 153000000.0 | 74.0 | 3.0 | 2.0 | 1.0 | Barranquilla | Miramar | 74.0 | 74.0 | 190000.0 | 4.0 | NaN | None | 5.0 | None | NaN |
| 407699 | -0.175781 | -77.255859 | Casa | 390000000.0 | 160.0 | 6.0 | 3.0 | 1.0 | Medellín | Occidente | 160.0 | 160.0 | NaN | 3.0 | 4.0 | 16 a 30 años | 1.0 | No Aplica | Occidente |
| 407696 | -0.148553 | NaN | Apartamento | 145000000.0 | 54.0 | 3.0 | 1.0 | 0.0 | Bogotá | Ciudad Tintal | 54.0 | 54.0 | NaN | 3.0 | NaN | 9 a 15 años | NaN | None | Ciudad Tintal |
ventas.info()
<class 'pandas.core.frame.DataFrame'>
Index: 169528 entries, 407717 to 34421
Data columns (total 19 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 lat 169191 non-null float64
1 long 149730 non-null float64
2 category 169528 non-null object
3 price 169515 non-null float64
4 surface 169526 non-null float64
5 rooms 167914 non-null float64
6 baths 168167 non-null float64
7 park 169528 non-null float64
8 ciudad 169528 non-null object
9 barrio 169528 non-null object
10 Área privada 120204 non-null float64
11 Área Const. 169526 non-null float64
12 Admón 105780 non-null float64
13 Estrato 167101 non-null float64
14 Estado 98550 non-null float64
15 Antiguedad 139813 non-null object
16 Piso No 101698 non-null float64
17 Tipo de Apartamento 57552 non-null object
18 Sector 134150 non-null object
dtypes: float64(13), object(6)
memory usage: 25.9+ MB
Análisis Descriptivo#
ventas.describe()
| lat | long | price | surface | rooms | baths | park | Área privada | Área Const. | Admón | Estrato | Estado | Piso No | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 169191.000000 | 149730.000000 | 1.695150e+05 | 1.695260e+05 | 167914.000000 | 168167.000000 | 169528.000000 | 1.202040e+05 | 1.695260e+05 | 1.057800e+05 | 167101.000000 | 98550.000000 | 101698.00000 |
| mean | 5.037213 | -74.856828 | 1.923391e+10 | 2.467323e+03 | 3.278226 | 2.719404 | 1.293946 | 1.948304e+03 | 2.467323e+03 | 1.824785e+06 | 4.354959 | 4.492532 | 4.19373 |
| std | 2.789628 | 0.884258 | 5.534035e+12 | 3.904603e+05 | 2.059622 | 1.523848 | 1.237870 | 5.486953e+05 | 3.904603e+05 | 3.205592e+07 | 1.280686 | 0.543905 | 3.11103 |
| min | -3.556624 | -78.530228 | 1.530000e+02 | 1.000000e+00 | 1.000000 | 1.000000 | 0.000000 | 1.000000e+00 | 1.000000e+00 | -1.794967e+09 | 1.000000 | 3.000000 | 1.00000 |
| 25% | 4.594397 | -75.567032 | 2.500000e+08 | 6.800000e+01 | 3.000000 | 2.000000 | 1.000000 | 6.700000e+01 | 6.800000e+01 | 1.750000e+05 | 3.000000 | 4.000000 | 2.00000 |
| 50% | 4.704000 | -74.197063 | 3.950000e+08 | 1.000000e+02 | 3.000000 | 2.000000 | 1.000000 | 1.000000e+02 | 1.000000e+02 | 3.150000e+05 | 4.000000 | 5.000000 | 3.00000 |
| 75% | 6.189920 | -74.066002 | 6.800000e+08 | 1.720000e+02 | 4.000000 | 3.000000 | 2.000000 | 1.720000e+02 | 1.720000e+02 | 5.600000e+05 | 6.000000 | 5.000000 | 5.00000 |
| max | 11.400610 | -67.130997 | 1.989188e+15 | 1.000000e+08 | 254.000000 | 253.000000 | 11.000000 | 1.900000e+08 | 1.000000e+08 | 1.900000e+09 | 6.000000 | 5.000000 | 16.00000 |
Análisis de datos atípicos#
Price#
# Crear un histograma del precio
fig = px.histogram(ventas, x='price', title='Histograma de precio',
labels={'price': 'Precio'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
Evidenemente existen muchos datos atipicos
# Crear un histograma del precio para cada estrato
fig = px.histogram(ventas, x='price', color='Estrato', barmode='overlay',
title='Histograma de precio por estrato',
labels={'price': 'Precio'},
opacity=0.75)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
# Crear un histograma del precio para cada ciudad
fig = px.histogram(ventas, x='price', color='ciudad', barmode='overlay',
title='Histograma de precio por ciudad',
labels={'price': 'Precio'},
opacity=0.75)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
# Calcular el primer y decimo cuartil
Q1 = ventas['price'].quantile(0.10)
Q3 = ventas['price'].quantile(0.90)
# Calcular el rango intercuartílico (IQR)
IQR = Q3 - Q1
# Definir los límites para los valores atípicos usando un rango más amplio
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# Identificar los valores atípicos
outliers = ventas[(ventas['price'] < lower_bound) | (ventas['price'] > upper_bound)]['price']
# Mostrar los valores atípicos y su cantidad
print("Valores atípicos en 'price':")
print(outliers)
print("\nCantidad de valores atípicos:", len(outliers))
# Imprimir porcentaje de valores atípicos
porc = (len(outliers) / len(ventas)) * 100
print("\nPorcentaje de valores atípicos:", porc)
Valores atípicos en 'price':
29757 3.300000e+09
29746 3.480000e+09
28208 4.500000e+09
28225 4.000000e+09
17364 2.900000e+09
...
408071 3.708000e+09
408062 5.269941e+09
407972 4.900000e+09
407993 5.582565e+09
161 1.200000e+12
Name: price, Length: 3086, dtype: float64
Cantidad de valores atípicos: 3086
Porcentaje de valores atípicos: 1.820348261054221
# Crear un DataFrame con solo los valores atípicos
outliers_df = ventas[(ventas['price'] < lower_bound) | (ventas['price'] > upper_bound)]
# Contar la cantidad de datos atípicos por ciudad
cantidad_atipicos_por_ciudad = outliers_df['ciudad'].value_counts().reset_index()
cantidad_atipicos_por_ciudad.columns = ['ciudad', 'cantidad']
# Gráfico de barras de la cantidad de datos atípicos por ciudad
fig = px.bar(cantidad_atipicos_por_ciudad, x='ciudad', y='cantidad', color='ciudad',
title='Cantidad de datos atípicos por ciudad')
fig.show()
# Contar la cantidad de datos atípicos por Estrato
cantidad_atipicos_por_e = outliers_df['Estrato'].value_counts().reset_index()
cantidad_atipicos_por_e.columns = ['Estrato', 'cantidad']
# Gráfico de barras de la cantidad de datos atípicos por Estrato
fig = px.bar(cantidad_atipicos_por_e, x='Estrato', y='cantidad', color='Estrato',
title='Cantidad de datos atípicos por Estrato')
# Calcular el promedio del precio por estrato
promedio_precio_por_estrato = outliers_df.groupby('Estrato')['price'].mean().reset_index()
promedio_precio_por_estrato.columns = ['Estrato', 'Promedio']
# Crear una figura con dos ejes y
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Agregar gráfico de barras para la cantidad de datos atípicos por Estrato
fig.add_trace(
go.Bar(x=cantidad_atipicos_por_e['Estrato'], y=cantidad_atipicos_por_e['cantidad'], name='Cantidad de datos atípicos', marker_color='rgb(55, 83, 109)'),
secondary_y=False,
)
# Agregar línea del promedio del precio por Estrato
fig.add_trace(
go.Scatter(x=promedio_precio_por_estrato['Estrato'], y=promedio_precio_por_estrato['Promedio'], name='Promedio del precio', line=dict(color='red'), mode='lines+markers'),
secondary_y=True,
)
# Agregar título y etiquetas de los ejes
fig.update_layout(
title_text="Cantidad de datos atípicos y Promedio del precio por Estrato",
xaxis_title="Estrato",
)
# Establecer nombres de los ejes y
fig.update_yaxes(title_text="Cantidad de datos atípicos", secondary_y=False)
fig.update_yaxes(title_text="Promedio del precio", secondary_y=True)
# Mostrar el gráfico
fig.show()
outliers_df.head()
| lat | long | category | price | surface | rooms | baths | park | ciudad | barrio | Área privada | Área Const. | Admón | Estrato | Estado | Antiguedad | Piso No | Tipo de Apartamento | Sector | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 29757 | 0.0 | NaN | Casa | 3.300000e+09 | 540.0 | 4.0 | 4.0 | 4.0 | Bogotá | Guaymaral | 540.0 | 540.0 | 1350000.0 | 6.0 | 5.0 | Menos de 1 año | 1.0 | No Aplica | Guaymaral |
| 29746 | 0.0 | NaN | Casa | 3.480000e+09 | 650.0 | 6.0 | 5.0 | 9.0 | Bogotá | Chapinero | NaN | 650.0 | NaN | 5.0 | 5.0 | 16 a 30 años | 2.0 | No Aplica | Chapinero |
| 28208 | 0.0 | NaN | Casa | 4.500000e+09 | 350.0 | 3.0 | 4.0 | 1.0 | Medellín | LAS PALMAS | NaN | 350.0 | NaN | 4.0 | NaN | None | NaN | No Aplica | NaN |
| 28225 | 0.0 | NaN | Casa | 4.000000e+09 | 740.0 | 4.0 | 5.0 | 4.0 | Cali | Santa Teresita | NaN | 740.0 | NaN | 6.0 | 5.0 | 9 a 15 años | 2.0 | No Aplica | Santa Teresita |
| 17364 | 0.0 | NaN | Apartamento | 2.900000e+09 | 464.0 | 4.0 | 5.0 | 4.0 | Bogotá | Los Rosales | 464.0 | 464.0 | 2300000.0 | 6.0 | 5.0 | Más de 30 años | 8.0 | None | Los Rosales |
Eliminación:
# Eliminar los outliers de la columna 'price'
ventas = ventas[(ventas['price'] >= lower_bound) & (ventas['price'] <= upper_bound)]
# Crear un histograma del precio despues de eliminar los atipicos
fig = px.histogram(ventas, x='price', title='Histograma de precio',
labels={'price': 'Precio'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
Rooms#
# Crear un histograma de rooms
fig = px.histogram(ventas, x='rooms', title='Histograma de cantidad de habitaciones',
labels={'rooms': 'Habitaciones'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Habitaciones', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
# Filtrar filas con valores de 'rooms' superiores a 50
rooms_mayor_50 = ventas[ventas['rooms'] > 50]
# Mostrar las filas resultantes
print(rooms_mayor_50)
lat long category price surface rooms baths \
383287 3.383238 -76.518471 Apartamento 140000000.0 61.0 254.0 2.0
287599 4.672591 -74.053879 Apartamento 870000000.0 123.0 253.0 253.0
251228 4.699450 -74.037437 Apartamento 880000000.0 185.0 66.0 3.0
163647 5.046391 -75.520950 Casa 190000000.0 72.0 60.0 3.0
147601 6.123374 -75.384232 Apartamento 450000000.0 80.0 116.0 2.0
116116 6.238554 -75.602364 Casa 920000000.0 247.0 253.0 3.0
park ciudad barrio Área privada Área Const. Admón \
383287 0.0 Cali El Caney NaN 61.0 150.0
287599 2.0 Bogotá El Virrey 115.0 123.0 580000.0
251228 2.0 Bogotá Santa Bárbara 166.0 185.0 600000.0
163647 0.0 Manizales VILLAMARIA 72.0 72.0 NaN
147601 1.0 Medellín SurOriente 80.0 80.0 232.0
116116 2.0 Medellín Laureles 279.0 247.0 NaN
Estrato Estado Antiguedad Piso No Tipo de Apartamento \
383287 4.0 5.0 Más de 30 años NaN None
287599 6.0 5.0 9 a 15 años 2.0 None
251228 6.0 4.0 16 a 30 años 3.0 None
163647 3.0 NaN 16 a 30 años NaN No Aplica
147601 6.0 NaN 1 a 8 años NaN None
116116 5.0 4.0 Más de 30 años NaN No Aplica
Sector
383287 Zona Sur
287599 Zona Norte
251228 Santa Bárbara
163647 NaN
147601 SurOriente
116116 Occidente
Evidentemente es un error, por ende, imputaremos con el promedio de las habitaciones de los inmuebles que tengan igual la cuidad, category y Estrato de inmueble.
# Definir una función para imputar el promedio de 'rooms'
def imputar_promedio_rooms(row):
if row['rooms'] > 50:
# Filtrar filas con la misma ciudad, categoría y estrato
filtro = ventas[(ventas['ciudad'] == row['ciudad']) &
(ventas['category'] == row['category']) &
(ventas['Estrato'] == row['Estrato'])]
# Calcular el promedio de 'rooms' para el grupo
promedio_rooms = filtro['rooms'].mean()
# Imputar el valor promedio
row['rooms'] = promedio_rooms
return row
# Aplicar la función para imputar los valores de 'rooms'
ventas = ventas.apply(imputar_promedio_rooms, axis=1)
# Crear un histograma de rooms
fig = px.histogram(ventas, x='rooms', title='Histograma de cantidad de habitaciones',
labels={'rooms': 'Habitaciones'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Habitaciones', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
Baths#
# Crear un histograma de baths
fig = px.histogram(ventas, x='baths', title='Histograma de cantidad de baños',
labels={'baths': 'Baños'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Baños', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
# Filtrar filas con valores de 'baths' superiores a 50
baths_mayor_30 = ventas[ventas['baths'] > 30]
# Mostrar las filas resultantes
print(baths_mayor_30)
lat long category price surface rooms \
33545 0.000000 NaN Casa 1.300000e+09 500.0 NaN
364862 3.461000 -76.528999 Casa 4.800000e+08 210.0 4.000000
339292 4.610315 -74.196404 Casa 3.000000e+08 391.0 11.000000
291837 4.669000 -74.041000 Apartamento 1.450000e+09 264.0 3.000000
287599 4.672591 -74.053879 Apartamento 8.700000e+08 123.0 2.739168
146184 6.178152 -75.572960 Apartamento 7.500000e+08 144.0 3.000000
67162 10.975321 -74.784279 Casa 4.900000e+08 484.0 30.000000
66742 10.979000 -74.800003 Casa 1.600000e+09 900.0 35.000000
baths park ciudad barrio Área privada \
33545 77.0 1.0 Medellín EL POBLADO NaN
364862 32.0 11.0 Cali Versalles 210.0
339292 44.0 1.0 Bogotá Bosa Los Naranjos 140.0
291837 52.0 2.0 Bogotá Chico Alto 264.0
287599 253.0 2.0 Bogotá El Virrey 115.0
146184 35.0 2.0 Medellín LOMA BENEDICTINOS 144.0
67162 32.0 0.0 Barranquilla Chiquinquirá 247.0
66742 33.0 0.0 Barranquilla Lucero 900.0
Área Const. Admón Estrato Estado Antiguedad Piso No \
33545 500.0 NaN 6.0 NaN None 1.0
364862 210.0 NaN 5.0 4.0 Más de 30 años 2.0
339292 391.0 NaN 2.0 4.0 16 a 30 años NaN
291837 264.0 1100000.0 6.0 4.0 16 a 30 años 4.0
287599 123.0 580000.0 6.0 5.0 9 a 15 años 2.0
146184 144.0 350000.0 5.0 NaN 1 a 8 años NaN
67162 484.0 NaN 3.0 4.0 16 a 30 años NaN
66742 900.0 NaN 3.0 4.0 1 a 8 años NaN
Tipo de Apartamento Sector
33545 No Aplica NaN
364862 No Aplica Zona Norte
339292 No Aplica Bosa Los Naranjos
291837 None Zona Norte
287599 None Zona Norte
146184 None Centro
67162 No Aplica Sur Oriente
66742 No Aplica Metropolitana
Evidentemente es un error, por ende, imputaremos con el promedio de las habitaciones de los inmuebles que tengan igual la cuidad, category y Estrato de inmueble.
# Definir una función para imputar el promedio de 'baths'
def imputar_promedio_baths(row):
if row['baths'] > 30:
# Filtrar filas con la misma ciudad, categoría y estrato
filtro = ventas[(ventas['ciudad'] == row['ciudad']) &
(ventas['category'] == row['category']) &
(ventas['Estrato'] == row['Estrato'])]
# Calcular el promedio de 'baths' para el grupo
promedio_baths = filtro['baths'].mean()
# Imputar el valor promedio
row['baths'] = promedio_baths
return row
# Aplicar la función para imputar los valores de 'baths'
ventas = ventas.apply(imputar_promedio_baths, axis=1)
# Crear un histograma de baths
fig = px.histogram(ventas, x='baths', title='Histograma de cantidad de baños',
labels={'baths': 'Baños'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Baños', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
Surface#
# Crear un histograma de 'surface'
fig = px.histogram(ventas, x='surface', title='Histograma de superficie',
labels={'surface': 'Superficie (m²)'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Superficie (m²)', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
# Filtrar filas con valores de 'surface' superiores a 50
surface_mayor_30 = ventas[ventas['surface'] > 100000]
# Mostrar las filas resultantes
print(surface_mayor_30)
lat long category price surface rooms \
24662 0.000000 NaN Casa 8.500000e+08 24100000.0 5.0
26382 0.000000 NaN Casa 5.900000e+08 245000.0 7.0
26263 0.000000 NaN Casa 6.000000e+08 152215.0 4.0
28891 0.000000 NaN Casa 3.500000e+08 6000000.0 5.0
29557 0.000000 NaN Casa 3.000000e+08 238806.0 7.0
27922 0.000000 NaN Casa 4.200000e+08 200000.0 4.0
30643 0.000000 NaN Casa 2.400000e+08 5501150.0 7.0
28278 0.000000 NaN Casa 3.900000e+08 6485355.0 4.0
16267 0.000000 NaN Apartamento 2.250000e+08 5762233.0 3.0
1978 0.000000 NaN Apartamento 7.500000e+08 139581.0 3.0
28263 0.000000 NaN Casa 3.950000e+08 88888888.0 2.0
396510 3.298540 -76.530930 Casa 4.000000e+08 170000.0 5.0
376989 3.407871 -76.550545 Casa 5.200000e+08 300300.0 4.0
371271 3.439657 -76.528442 Casa 3.850000e+08 300000.0 6.0
365076 3.459028 -76.535065 Casa 9.500000e+08 220000.0 6.0
358402 4.487996 -74.098907 Casa 1.500000e+08 99999999.0 9.0
347550 4.590851 -74.083054 Casa 7.000000e+08 190000.0 5.0
344875 4.597900 -74.106216 Casa 5.650000e+08 565000.0 6.0
322897 4.638382 -74.088768 Casa 4.000000e+08 111111.0 5.0
322403 4.638403 -74.088768 Casa 2.700000e+08 111111.0 8.0
321518 4.639140 -74.148041 Apartamento 6.500000e+08 130000.0 7.0
320436 4.640376 -74.057983 Apartamento 3.600000e+08 36000000.0 1.0
297792 4.663249 -74.062195 Casa 9.000000e+08 265000.0 6.0
266763 4.690610 -74.107170 Casa 4.500000e+08 122000.0 4.0
261755 4.693690 -74.099228 Casa 5.500000e+08 195000.0 8.0
238532 4.706289 -74.036522 Apartamento 8.600000e+08 71818181.0 3.0
230309 4.713731 -74.097633 Casa 3.500000e+08 180000.0 6.0
228920 4.714928 -74.104393 Casa 3.200000e+08 123456.0 7.0
219876 4.721099 -74.073181 Casa 6.500000e+08 158000.0 4.0
184758 4.755840 -74.052406 Casa 7.500000e+08 180800.0 4.0
184440 4.756157 -74.045708 Apartamento 6.390000e+08 105000.0 3.0
175052 4.804483 -75.687645 Apartamento 8.500000e+08 25605222.0 5.0
167232 5.026003 -74.030014 Casa 3.300000e+08 111111.0 7.0
166693 5.026027 -74.030014 Casa 1.000000e+08 865225.0 6.0
166300 5.030080 -75.457657 Casa 2.300000e+08 111111.0 4.0
133378 6.202770 -75.554443 Apartamento 1.600000e+09 270000.0 4.0
130907 6.206545 -75.576408 Apartamento 4.400000e+08 418000.0 4.0
112029 6.243726 -75.610153 Apartamento 1.500000e+09 347347.0 4.0
91712 10.384208 -75.482521 Casa 2.700000e+08 330000.0 3.0
89342 10.392638 -75.546875 Apartamento 1.820000e+09 182182.0 3.0
69898 10.928613 -74.807098 Casa 1.000000e+08 123889.0 2.0
63393 10.990490 -74.819481 Apartamento 4.500000e+08 332000.0 3.0
45182 11.011632 -74.822914 Apartamento 1.240000e+09 561535.0 4.0
42323 11.014119 -74.813507 Apartamento 4.250000e+08 154250.0 3.0
baths park ciudad barrio Área privada \
24662 3.0 2.0 Bogotá Villa Claudia 155000.0
26382 4.0 1.0 Bogotá Castilla 137000.0
26263 3.0 2.0 Bogotá Cedrito Gold NaN
28891 5.0 0.0 Bogotá Zona Sur NaN
29557 3.0 0.0 Bogotá Tunjuelito NaN
27922 3.0 0.0 Medellín Villa Hermosa 200000.0
30643 3.0 0.0 Cali Alirio mora NaN
28278 2.0 0.0 Bogotá Portales del norte NaN
16267 2.0 0.0 Medellín Itagui san Pio NaN
1978 4.0 0.0 Bogotá Santa Bárbara NaN
28263 1.0 0.0 Bogotá Ciudad Kennedy Central 8888888.0
396510 5.0 3.0 Cali Zona Sur 170.0
376989 3.0 3.0 Cali Cuarto de Legua 300.0
371271 4.0 1.0 Cali Aranjuez 300000.0
365076 3.0 3.0 Cali 220000.0
358402 3.0 0.0 Bogotá Usme NaN
347550 3.0 2.0 Bogotá Ciudad Montes NaN
344875 4.0 2.0 Bogotá Santa Isabel NaN
322897 2.0 0.0 Barranquilla Buena Esperanza 111111.0
322403 3.0 0.0 Bogotá Patio Bonito 111111.0
321518 5.0 0.0 Bogotá Techo 130.0
320436 2.0 2.0 Bogotá Zona Chapinero 45.0
297792 4.0 0.0 Bogotá San Felipe NaN
266763 3.0 0.0 Bogotá La clarita NaN
261755 4.0 0.0 Bogotá Tabora 195.0
238532 3.0 2.0 Bogotá La Carolina NaN
230309 4.0 0.0 Bogotá Bachue 180000.0
228920 3.0 0.0 Bogotá Bachue 123456.0
219876 4.0 2.0 Bogotá Ciudad Jardin Norte 158000.0
184758 3.0 2.0 Bogotá Villa Del Prado 180800.0
184440 3.0 2.0 Bogotá Nueva Autopista 105000.0
175052 5.0 4.0 Pereira PINARES NaN
167232 4.0 0.0 Bogotá Bachue 111111.0
166693 2.0 0.0 Bogotá La paz sur 865225.0
166300 1.0 0.0 Manizales La Enea NaN
133378 5.0 3.0 Medellín El Poblado 270.0
130907 2.0 1.0 Medellín PATIO BONITO 136.0
112029 5.0 2.0 Medellín LAURELES 347.0
91712 2.0 0.0 Cartagena El El Socorro 330000.0
89342 3.0 2.0 Cartagena CASTILLOGRANDE 182.0
69898 1.0 0.0 Barranquilla Soledad 2000 123889.0
63393 4.0 1.0 Barranquilla Ciudad Jardín NaN
45182 4.0 4.0 Barranquilla ALTOS DEL LIMONAR NaN
42323 3.0 1.0 Barranquilla Altos De Riomar NaN
Área Const. Admón Estrato Estado Antiguedad Piso No \
24662 24100000.0 NaN 3.0 4.0 Más de 30 años 2.0
26382 245000.0 NaN 3.0 5.0 Más de 30 años 2.0
26263 152215.0 NaN 4.0 5.0 16 a 30 años 3.0
28891 6000000.0 50000.0 2.0 3.0 Más de 30 años 3.0
29557 238806.0 NaN 2.0 3.0 16 a 30 años 2.0
27922 200000.0 5000.0 3.0 4.0 None NaN
30643 5501150.0 NaN NaN NaN None NaN
28278 6485355.0 NaN NaN NaN None NaN
16267 5762233.0 NaN NaN NaN None NaN
1978 139581.0 NaN NaN NaN None NaN
28263 88888888.0 NaN 3.0 5.0 1 a 8 años 2.0
396510 170000.0 280000.0 4.0 5.0 1 a 8 años 2.0
376989 300300.0 NaN 5.0 5.0 16 a 30 años 1.0
371271 300000.0 NaN 3.0 3.0 1 a 8 años 2.0
365076 220000.0 NaN 1.0 5.0 None NaN
358402 99999999.0 NaN 2.0 4.0 1 a 8 años 4.0
347550 190000.0 NaN 3.0 NaN None NaN
344875 565000.0 NaN 3.0 4.0 9 a 15 años 3.0
322897 111111.0 NaN 3.0 NaN Más de 30 años NaN
322403 111111.0 NaN 2.0 NaN None NaN
321518 130000.0 NaN 2.0 3.0 1 a 8 años 6.0
320436 36000000.0 NaN 5.0 5.0 9 a 15 años 2.0
297792 265000.0 NaN 3.0 3.0 None NaN
266763 122000.0 NaN NaN NaN None NaN
261755 195000.0 NaN 3.0 4.0 16 a 30 años 2.0
238532 71818181.0 800000.0 6.0 5.0 1 a 8 años 8.0
230309 180000.0 NaN 2.0 5.0 16 a 30 años NaN
228920 123456.0 NaN 2.0 NaN None NaN
219876 158000.0 407000.0 4.0 NaN None NaN
184758 180800.0 NaN 4.0 NaN None NaN
184440 105000.0 550000.0 5.0 5.0 1 a 8 años 3.0
175052 25605222.0 NaN 6.0 5.0 9 a 15 años 8.0
167232 111111.0 NaN 2.0 NaN 16 a 30 años NaN
166693 865225.0 NaN 1.0 NaN None NaN
166300 111111.0 NaN 3.0 NaN 16 a 30 años NaN
133378 270000.0 674000.0 6.0 5.0 9 a 15 años 1.0
130907 418000.0 NaN 6.0 NaN 16 a 30 años NaN
112029 347347.0 900000.0 5.0 NaN 9 a 15 años NaN
91712 330000.0 NaN 3.0 NaN Más de 30 años NaN
89342 182182.0 NaN 6.0 NaN 9 a 15 años 8.0
69898 123889.0 NaN 2.0 NaN 9 a 15 años NaN
63393 332000.0 NaN 5.0 5.0 1 a 8 años 5.0
45182 561535.0 NaN 6.0 NaN None NaN
42323 154250.0 0.0 6.0 4.0 16 a 30 años NaN
Tipo de Apartamento Sector
24662 No Aplica Zona Sur
26382 No Aplica Castilla
26263 No Aplica NaN
28891 No Aplica Zona Sur
29557 No Aplica Tunjuelito
27922 No Aplica Villa Hermosa
30643 No Aplica NaN
28278 No Aplica NaN
16267 None NaN
1978 None Santa Bárbara
28263 No Aplica Ciudad Kennedy Central
396510 No Aplica Zona Sur
376989 No Aplica Zona Sur
371271 No Aplica Aranjuez
365076 No Aplica NaN
358402 No Aplica Usme
347550 No Aplica Ciudad Montes
344875 No Aplica Santa Isabel
322897 No Aplica NaN
322403 No Aplica Patio Bonito
321518 Duplex Techo
320436 None Zona Chapinero
297792 No Aplica San Felipe
266763 No Aplica NaN
261755 No Aplica Tabora
238532 PentHouse La Carolina
230309 No Aplica Bachue
228920 No Aplica Bachue
219876 No Aplica Ciudad Jardin Norte
184758 No Aplica Villa Del Prado
184440 None Nueva Autopista
175052 PentHouse Circunvalar - Alamos
167232 No Aplica Bachue
166693 No Aplica Zona Sur
166300 No Aplica NaN
133378 None El Poblado
130907 None NaN
112029 Duplex NaN
91712 No Aplica NaN
89342 None NaN
69898 No Aplica NaN
63393 None Norte Centro Histórico
45182 None NaN
42323 None NaN
# Definir una función para imputar el promedio de 'surface'
def imputar_promedio_surface(row):
if row['surface'] > 10000:
# Filtrar filas con la misma ciudad, categoría y estrato
filtro = ventas[(ventas['ciudad'] == row['ciudad']) &
(ventas['category'] == row['category']) &
(ventas['Estrato'] == row['Estrato'])]
# Calcular el promedio de 'surface' para el grupo
promedio_surface = filtro['surface'].mean()
# Imputar el valor promedio
row['surface'] = promedio_surface
return row
# Aplicar la función para imputar los valores de 'surface'
ventas = ventas.apply(imputar_promedio_surface, axis=1)
# Crear un histograma de 'surface'
fig = px.histogram(ventas, x='surface', title='Histograma de superficie',
labels={'surface': 'Superficie (m²)'},
opacity=0.8)
# Personalizar el gráfico
fig.update_layout(xaxis_title='Superficie (m²)', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')
# Mostrar el gráfico
fig.show()
Análisis de datos nulos o faltantes#
# DataFrame para el análisis de datos nulos
analisis_nulos = pd.DataFrame({
'Conteo': ventas.isnull().sum(),
'Porcentaje': (ventas.isnull().sum() / len(ventas)) * 100
})
print(analisis_nulos)
Conteo Porcentaje
lat 325 0.195278
long 19548 11.745549
category 0 0.000000
price 0 0.000000
surface 9 0.005408
rooms 1510 0.907294
baths 1248 0.749869
park 0 0.000000
ciudad 0 0.000000
barrio 0 0.000000
Área privada 48636 29.223272
Área Const. 2 0.001202
Admón 62404 37.495869
Estrato 2414 1.450468
Estado 69801 41.940407
Antiguedad 29276 17.590684
Piso No 66273 39.820584
Tipo de Apartamento 110967 66.675279
Sector 34869 20.951277
# Gráfico para el conteo de valores nulos
fig_conteo = px.bar(analisis_nulos, x=analisis_nulos.index, y='Conteo', title='Conteo de valores nulos por columna')
fig_conteo.update_layout(xaxis_title='Columnas', yaxis_title='Conteo de valores nulos', xaxis_tickangle=-45)
fig_conteo.show()
# Gráfico para el porcentaje de valores nulos
fig_porcentaje = px.bar(analisis_nulos, x=analisis_nulos.index, y='Porcentaje', title='Porcentaje de valores nulos por columna')
fig_porcentaje.update_layout(xaxis_title='Columnas', yaxis_title='Porcentaje de valores nulos (%)', xaxis_tickangle=-45)
fig_porcentaje.show()
# Mapa de calor de datos faltantes
plt.figure(figsize=(12, 6))
sns.heatmap(ventas.isnull(), cbar=False, cmap='Blues_r')
plt.title('Mapa de calor de datos faltantes')
plt.xticks(rotation=45, ha='right', fontsize=12)
plt.yticks(fontsize=12)
plt.show()
# Establecer estilo de gráfico
plt.style.use('ggplot')
# Crear el dendrograma de datos faltantes
msno.dendrogram(ventas, figsize=(12, 6))
# Personalizar el gráfico
plt.title('Dendrograma de datos faltantes', fontsize=16)
plt.ylabel('Número de datos faltantes', fontsize=14)
plt.xlabel('Columnas', fontsize=14)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
# Mostrar el gráfico
plt.show()
Imputación/eliminación de datos nulos#
Eliminación:
# Eliminar filas donde 'price' es nulo
ventas = ventas.dropna(subset=['price'])
# Eliminar filas donde 'rooms' es nulo
ventas = ventas.dropna(subset=['rooms'])
# Eliminar filas donde 'baths' es nulo
ventas = ventas.dropna(subset=['baths'])
# Eliminar filas donde 'Área Const.' es nulo
ventas = ventas.dropna(subset=['Área Const.'])
Imputar latitudes y longitudes de acuerdo con las latitudes y longitudes de las viviendas que estén ubicadas en la misma cuidad y sector.
# Calcular las coordenadas promedio por ciudad y sector
coordenadas_promedio = ventas.groupby(['ciudad', 'Sector']).agg({
'lat': 'mean',
'long': 'mean'
}).reset_index()
# Renombrar columnas para evitar conflictos durante el merge
coordenadas_promedio = coordenadas_promedio.rename(columns={'lat': 'lat_promedio', 'long': 'long_promedio'})
# Unir el DataFrame original con las coordenadas promedio
ventas = ventas.merge(coordenadas_promedio, on=['ciudad', 'Sector'], how='left')
# Imputar los valores faltantes con las coordenadas promedio
ventas['lat'] = ventas.apply(lambda row: row['lat_promedio'] if pd.isnull(row['lat']) else row['lat'], axis=1)
ventas['long'] = ventas.apply(lambda row: row['long_promedio'] if pd.isnull(row['long']) else row['long'], axis=1)
# Eliminar las columnas de coordenadas promedio
ventas = ventas.drop(columns=['lat_promedio', 'long_promedio'])
# Calcular las coordenadas promedio por ciudad y barrio
coordenadas_promedio = ventas.groupby(['ciudad', 'barrio']).agg({
'lat': 'mean',
'long': 'mean'
}).reset_index()
# Renombrar columnas para evitar conflictos durante el merge
coordenadas_promedio = coordenadas_promedio.rename(columns={'lat': 'lat_promedio', 'long': 'long_promedio'})
# Unir el DataFrame original con las coordenadas promedio
ventas = ventas.merge(coordenadas_promedio, on=['ciudad', 'barrio'], how='left')
# Imputar los valores faltantes con las coordenadas promedio
ventas['lat'] = ventas.apply(lambda row: row['lat_promedio'] if pd.isnull(row['lat']) else row['lat'], axis=1)
ventas['long'] = ventas.apply(lambda row: row['long_promedio'] if pd.isnull(row['long']) else row['long'], axis=1)
# Eliminar las columnas de coordenadas promedio
ventas = ventas.drop(columns=['lat_promedio', 'long_promedio'])
# Gráfico para el conteo de valores nulos
fig_conteo = px.bar(analisis_nulos, x=analisis_nulos.index, y='Conteo', title='Conteo de valores nulos por columna')
fig_conteo.update_layout(xaxis_title='Columnas', yaxis_title='Conteo de valores nulos', xaxis_tickangle=-45)
fig_conteo.show()
# Gráfico para el porcentaje de valores nulos
fig_porcentaje = px.bar(analisis_nulos, x=analisis_nulos.index, y='Porcentaje', title='Porcentaje de valores nulos por columna')
fig_porcentaje.update_layout(xaxis_title='Columnas', yaxis_title='Porcentaje de valores nulos (%)', xaxis_tickangle=-45)
fig_porcentaje.show()
print(analisis_nulos)
Conteo Porcentaje
lat 325 0.195278
long 19548 11.745549
category 0 0.000000
price 0 0.000000
surface 9 0.005408
rooms 1510 0.907294
baths 1248 0.749869
park 0 0.000000
ciudad 0 0.000000
barrio 0 0.000000
Área privada 48636 29.223272
Área Const. 2 0.001202
Admón 62404 37.495869
Estrato 2414 1.450468
Estado 69801 41.940407
Antiguedad 29276 17.590684
Piso No 66273 39.820584
Tipo de Apartamento 110967 66.675279
Sector 34869 20.951277
Análisis Exploratorio de Datos#
# Crear el reporte de análisis exploratorio de datos
reporte = sv.analyze(ventas, target_feat='price')
# Mostrar el reporte en el notebook
reporte.show_notebook()
Boxplot
fig = px.box(ventas, x='category', y='price')
fig.show()
fig = px.box(ventas, x='ciudad', y='price')
fig.show()